/*
 *  objects-array.h
 *  joosVM
 *
 *  Created by Smári Waage on 14.8.2009.
 *  Copyright 2009 smari.org. All rights reserved.
 *
 *  This program is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU General Public License
 *  as published by the Free Software Foundation; either version 2
 *  of the License, or (at your option) any later version.
 *  
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *  
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA. *
 */

#ifndef objects_array___ 
#define objects_array___ 

#include "objects-object.h"


/*
 * Array
 *
 * |	+HeapObject			|
 * +------------------------+
 * |		length			|
 * +------------------------+
 * |		Array			|
 * |		.				|
 * |		.				|
 * +------------------------+
 */
class Array : public HeapObject {
	static const int kLengthOffset = HeapObject::kHeaderSize;
protected:
	static unsigned int bytealign(unsigned int size) {
		// TODO: More effective 
		return size + (size % 4 > 0 ? 4 - (size % 4) : 0);
	}
public:
	static const int kHeaderSize = kLengthOffset + kPointerSize;
	
	// the length of the variable sized part in bytes!!
	int getVariableLength() { return ((Smi*)field(Array::kLengthOffset))->value(); }
	void setVariableLength(int length) { setField(Array::kLengthOffset, Smi::fromInt(length)); }
	
	int array_length();
};


/*			Array
 * +------------------------+
 * |	ArrayType			|
 * +------------------------+
 * |		Array			|
 * |		.				|
 * |		.				|
 * +------------------------+
*/

class StaticArray : public Array {
public:
	static const int kArrayType = Array::kHeaderSize;
	static const int kArrayOffset = kArrayType + kPointerSize;
	
	static StaticArray* Initalize(int length){
		//It sounds stubit to allow array of size 0, but it eliminates the special case
		REQUIRE(length >= 0)
		int len = length * kPointerSize;
		StaticArray* arr = (StaticArray*) HeapObject::allocHeapObject(StaticArray::kArrayOffset + len);
		arr->setInternalType(Smi::fromInt(IType_StaticArray));
		arr->setVariableLength(len);
		arr->setArrayType((HeapObject*)Smi::fromInt(IType_StaticArray));
		arr->clear();
		return arr;
	}
	void clear(){
		memset(address()+kArrayOffset, 0x00, getVariableLength());//initalize to NULL
	}
	// Setter and getter for elements.
	Pointer* get(int index) { 
		REQUIRE(index >=0 && index < array_length() && getInternalType()->value() == IType_StaticArray)
		// TODO: if (isOutOfBounds(index)) signal error
		return (Pointer*)field(kArrayOffset + index * kPointerSize); 
	}
	
	void set(int index, Pointer* value) { 
		REQUIRE(index >=0 && index < array_length() )
		// TODO: if (isOutOfBounds(index)) signal error
		setField(kArrayOffset + index * kPointerSize, value); 
	}
	
	HeapObject* getArrayType(){
		return (HeapObject*)field(kArrayType);
	}
	void setArrayType(HeapObject* type){
		setField(kArrayType, type);
	}
	
	StaticArray* resizeRelocate(int additionalSpace){
		StaticArray* n = StaticArray::Initalize(array_length() + additionalSpace);
		n->clear();
		memcpy(n->address()+kArrayOffset, address()+kArrayOffset, getVariableLength());
		return n;
	}
	
	//number of elements in the array
	int array_length() { return getVariableLength() / kPointerSize; }
	
	//bytes of the object
	int staticarray_size(){ return kArrayOffset + getVariableLength();}
	
	void staticarray_scan(Pointer** nHP){
		dout(debug_gc,"scan staticarray\n",0);
		//I'm in toSpace just need to check if my fields are not all here else fw them and update the pointer
		if(getArrayType()->isHeapObject() || (((Smi*)getArrayType())->value() == IType_StaticArray)){//check if it is an 
			setField(kArrayType,getArrayType()->forward(nHP));
			for(int i=0;i< array_length();i++){
				Pointer* o = get(i);
				if(o->isHeapObject()){
					set(i,((HeapObject*)o)->forward(nHP));
				}
			}
		}
	}
	
};

class TaggedArray : public Array {
public:
	static const int kArrayType = Array::kHeaderSize;
	static const int kArrayOffset = kArrayType + kPointerSize;
	
	static TaggedArray* Initalize(int length){
		//It sounds stubit to allow array of size 0, but it eliminates the special case
		REQUIRE(length >= 0)
		int len = length *k2PointerSize; //double the size to tagg each value.
		TaggedArray* arr = (TaggedArray*) HeapObject::allocHeapObject(kArrayOffset + len);
		arr->setInternalType(Smi::fromInt(IType_TaggedArray));
		arr->setVariableLength(len);
		arr->setArrayType((HeapObject*)Smi::fromInt(IType_Array));
		arr->clear();
		return arr;
	}
	void clear(){
		memset(address()+kArrayOffset, 0x00, getVariableLength());//initalize to NULL
	}
	// Setter and getter for elements.
	Pointer* get(int index) { 
		REQUIRE(index >=0 && index < array_length() && getInternalType()->value() == IType_TaggedArray)
		// TODO: if (isOutOfBounds(index)) signal error
		return (Pointer*)field(kArrayOffset + index * k2PointerSize); 
	}
	
	void update(int index, Pointer* value){
		REQUIRE(index >=0 && index < array_length() && getInternalType()->value() == IType_TaggedArray)
		int idx = kArrayOffset + index * k2PointerSize;
		setField(idx, value);
	}
	
	void setPrim(int index, Pointer* value) { 
		REQUIRE(index >=0 && index < array_length() && getInternalType()->value() == IType_TaggedArray)
		//u8 a = ((u8)1<<32);
		//*(u8*)(address() + kArrayOffset + (index * kDoubleSize)) = a |(u8)value;
		int idx = kArrayOffset + index * k2PointerSize;
		setField(idx, value);
		setField(idx+kPointerSize, (Pointer*)0);
		//*(byte*)(address() + idx +kPointerSize) = 0;
		ENSURE(get(index) == value && (int)field(idx+kPointerSize) == 0)
		
	}
	void setRef(int index, Pointer* value){
		REQUIRE(index >=0 && index < array_length() && getInternalType()->value() == IType_TaggedArray)
		//*(u8*)(address() + kArrayOffset + index * kDoubleSize) = (u8)value;
		int idx = kArrayOffset + index * k2PointerSize;
		setField(idx, value);
		setField(idx+kPointerSize, (Pointer*)1);
		//*(byte*)(address() + idx +kPointerSize) = 1;
		ENSURE(get(index) == value && (int)field(idx+kPointerSize) == 1)
	}
	HeapObject* getArrayType(){
		return (HeapObject*)field(kArrayType);
	}
	void setArrayType(HeapObject* type){
		setField(kArrayType, type);
	}
	
	//number of elements in the array
	int array_length() { return getVariableLength()/ k2PointerSize; }
	
	//bytes of the object
	int taggedarray_size(){ return kArrayOffset + getVariableLength();}
	
	void taggedarray_scan(Pointer** nHP){
		dout(debug_gc,"scan taggedarray\n");
		//I'm in toSpace just need to check if my fields are not all here else fw them and update the pointer
		//char b = ((Smi*)getArrayType())->value();

		for(int i=0;i<array_length();i++){
			
			//Todo: moveit up to the loop
			int idx = kArrayOffset + kPointerSize + (i * k2PointerSize);
			Pointer* c = field(idx);
			//dout(1,"%p %p",c,field(idx-kPointerSize))
			if((int)c == 1){
				HeapObject* p = (HeapObject*)field(idx -kPointerSize);
				if(p != NULL && (p-1) != 0) //check if its a null 
					setField(idx-kPointerSize, (HeapObject*)p->forward(nHP));
			}
			//dout(1," %p\n",field(idx-kPointerSize))
		}
	}
	
};


//ToDo: move this...
class TaggedValue : public HeapObject {
public:
	static const int kTaggingOffset = HeapObject::kHeaderSize;
	static const int kValueOffset = kTaggingOffset + kPointerSize;
	static const int kHeaderSize = kValueOffset + kPointerSize;
	
	static TaggedValue* Initalize(Pointer* value, bool referance){
		TaggedValue* v = (TaggedValue*) HeapObject::allocHeapObject(kHeaderSize);
		v->setInternalType(Smi::fromInt(IType_TaggedValue));
		v->setValue(value);
		v->setIsRef(referance);
		return v;
	}
	bool isReferance(){
		return (bool)field(kTaggingOffset);
	}
	void setIsRef(bool referance){
		setField(kTaggingOffset, (Pointer*)referance);
	}
	void setValue(Pointer* value){
		setField(kValueOffset, value);
	}
	Pointer* getValue(){
		return field(kValueOffset);
	}
	
	int taggedvalue_size(){return kHeaderSize;}
	void taggedvalue_scan(Pointer** nHP){
		if(isReferance()){
			setValue(((HeapObject*)getValue())->forward(nHP));
		}
	}
};


class ByteArray : public Array {
	
protected:
	//Todo: clever way to store the padding 0-3
	static const int kUnalignedLengthOffset = Array::kHeaderSize;	
	static const int kArrayOffset = kUnalignedLengthOffset + kPointerSize;
	
	byte* addrAt(int index) { 
		REQUIRE(index >= 0 && index < array_length())
		return (byte*)(address() + kArrayOffset + index); 
	}
public:
	
	static ByteArray* Initialize(int length){
		REQUIRE(length >= 0)
		int alignedlength = bytealign(length);
		ByteArray* arr = (ByteArray*)HeapObject::allocHeapObject(kArrayOffset +alignedlength);
		arr->setInternalType(Smi::fromInt(IType_ByteArray));
		arr->setVariableLength(alignedlength);
		arr->setField(kUnalignedLengthOffset, Smi::fromInt(length));
		ENSURE(arr->size() % 4 == 0)
		return arr;
	}
	
	void clear(){
		memset(address()+kArrayOffset, 0x00, getVariableLength());//initalize to NULL
	}
	byte get(int index) { 
		return *addrAt(index); 
	}
	
	void set(int index, byte value) { 
		*addrAt(index) = value;
		ENSURE(value == get(index))
	}
	
	int array_length() { return ((Smi*)field(kUnalignedLengthOffset))->value(); }
	
	int bytearray_size(){ return kArrayOffset + getVariableLength();}
	//cant contain ref's so there is nothing to see
	void bytearray_scan(Pointer** nHP){ return;}
};


class String : public ByteArray {
	//Todo: store the hash
	//static const int kHashOffset = ByteArray::kHeaderSize + kPointerSize;
	
public:
	static String* Initialize(int length){
		int alignedlength = bytealign(length+1);
		String* str = (String*)HeapObject::allocHeapObject(kArrayOffset + alignedlength);
		str->setInternalType(Smi::fromInt(IType_String));
		str->setField(kUnalignedLengthOffset, Smi::fromInt(length));
		str->setVariableLength(alignedlength);
		ENSURE(str->size() % 4 == 0)
		return str;
	}
	static String* Initialize(const char* value, int length){
		REQUIRE(strlen(value) +1 == length)
		String* str = String::Initialize(length);
		str->setStr(value);
		return str;
	}
	
	static String* charToString(const char* value){
		return String::Initialize(value,strlen(value)+1);
	}
	
	const char* str() { return (char*)addrAt(0); }
	void setStr(const char* value) { 
		REQUIRE(strlen(value)+1 <= string_length())
		// TODO: memcmp
		int i;
		for (i = 0 ; value[i] != '\0' ; i++) {
			*addrAt(i) = value[i];
		}
		// Add '\'
		*addrAt(i) = value[i];
	}
	void insertStr(const char* value, int position){
		REQUIRE(strlen(value)+position+1 <= string_length())
		
		int i;
		for (i = 0 ; value[i] != '\0' ; i++) {
			*addrAt(i+position) = value[i];
		}
		// Add '\'
		*addrAt(i+position) = value[i];
	}
	
	int string_length() { return ((Smi*)field(kUnalignedLengthOffset))->value(); }
	bool strEqual(String* st){
		int  len = string_length();
		if(len != st->string_length()) return false;
		
		for (int i = 0 ; i<len; i++) {
			if(*addrAt(i) != st->get(i))
				return false;
		}
		return true;
	}
	
	unsigned int hash(){
		unsigned int hash = 0;// = (unsigned int)field(kHashOffset);
		//if(hash == 0){
		// Note: the Jenkins one-at-a-time hash function
		for (int i = 0 ; str()[i] != '\0' ; i++) {
			signed int r = str()[i];
			hash += r;
			hash += (hash << 10);
			hash ^= (hash >> 6);
		}
		hash += (hash << 3);
		hash ^= (hash >> 11);
		hash += (hash << 15);
		//setField(kHashOffset, (Pointer*)hash);
		//}
		return hash;
	}
	
	int string_size(){ return kArrayOffset + getVariableLength();}
	//nothing to see.
	void string_scan(Pointer** nHP){dout(debug_gc,"scan string \n",0) return;}
	
};


/*
 * HashTable
 *
 * |	+Array				|
 * +------------------------+
 * |	capacity			|
 * +------------------------+
 * |		.				|
 * +						+
 * |		.				|
 * +------------------------+
 *
 * Note: no raw primitives allowed
 */
class HashTable : public HeapObject {
	static const int kCapacityOffset = HeapObject::kHeaderSize;
	static const int kElementOffset = kCapacityOffset + kPointerSize;
	
	static const int kElementSize = 2;
protected:
	
	String* keyAt(int index){
		REQUIRE(index >= 0 && index <capacity())
		return (String*)field(kElementOffset + index * kElementSize * kPointerSize);
	}
	
	void setKeyAt(int index, String* key) { 
		int idx = kElementOffset + index * kElementSize * kPointerSize; 
		REQUIRE(index >= 0 && index <capacity() && kElementOffset <= idx && kElementOffset+(capacity() * kElementOffset * kPointerSize))
		setField(idx, key); 
		ENSURE( key == keyAt(index))
	};
	
	Pointer* valueAt(int index) { 
		REQUIRE(index >= 0 && index <capacity())
		return (Pointer*)field(kElementOffset + (index * kElementSize + 1) * kPointerSize); 
	}
	
	void setValueAt(int index, Pointer* value){ 
		int idx = kElementOffset + (index * kElementSize + 1) * kPointerSize; 
		REQUIRE(index >= 0 && index <capacity() && kElementOffset <= idx && kElementOffset+(capacity() * kElementOffset * kPointerSize))
		setField(idx, value); 
		ENSURE( value == valueAt(index))
	};
	
	int find_slot(String* key) {
		unsigned int z = key->hash();
		unsigned int x = capacity();
		if(x <=0) return -1; //capacity is 0 so nothing is there,
		unsigned int i = z % x;
		// search until we either find the key, or find an empty slot.
		int b = capacity();
		while ((keyAt(i) != NULL) && ( strcmp(keyAt(i)->str(), key->str()) != 0 )) {
			//made a full circle
			if(0>b--) return -1;
			
			// Linear probing
			i = (i + 1) % capacity();
		}
		return i;
	}
	
public:
	static HashTable* Initialize(int capacity){
		REQUIRE(capacity >= 0)
		//not all and some have 0, capacity += capacity*0.25;//Most of the funtions that create the table internally know there size 
		HashTable* h = (HashTable*)HeapObject::allocHeapObject(kElementOffset + capacity * kElementSize * kPointerSize);
		h->setInternalType(Smi::fromInt(IType_HashTable));
		h->setCapacity(capacity);
		// zero out hashmap memory, dont see a good way of iterating it else.
		//Todo: figure out a better way.
		memset(h->address()+kElementOffset, 0x00, capacity * kElementSize * kPointerSize);
		return h;
	}
	
	int capacity() { return ((Smi*)field(kCapacityOffset))->value(); };
	void setCapacity(int capacity) { setField(kCapacityOffset, Smi::fromInt(capacity)); };
	
	Pointer* lookup(String* key){
		REQUIRE(getInternalType()->value() == IType_HashTable || getInternalType()->value() == IType_MethodArea)
		int i = find_slot(key);
		if (i>=0 && keyAt(i) != NULL) {// key is in table
			return valueAt(i); 
		} else { // key is not in table
			return NULL; //TODO: make undefined/ or internal null.
		}
	}
	
	void set(String* key, Pointer* value) {
		REQUIRE(key != NULL &&  (getInternalType()->value() == IType_HashTable|| getInternalType()->value() == IType_MethodArea))
		int i = find_slot(key);
		if(i<0){
			// TODO: Re-allocate and expand
			//if ( almost full)
			//rebuild the table larger
			//i = find_slot(key)
			
			// increase the size of the table....
			printf("HashTable: tying to exceed capacity");
			abort();
		}if (keyAt(i) != NULL) { // key is in table
			setValueAt(i, value);
		} else {
			setKeyAt(i, key);
			setValueAt(i, value);
		}
	}
	
	void printHashTable(){
		int end = capacity()*kElementSize;
		ddout("size %i \n", end);
		for(int i=0; i < end;i++){
			int idx = kElementOffset + i * kPointerSize;
			Pointer* itr = field(idx);
			if(itr != NULL && itr->isHeapObject()){
				if(((HeapObject*)itr)->getInternalType()->value() == IType_String)
					printf("done %s - \n",((String*)itr)->str());
				else {
					printf("value %i\n",((HeapObject*)itr)->getInternalType()->value());
				}
			}
		}
		
	}
	
	int hashtable_size(){ return kElementOffset + (capacity() * kElementSize * kPointerSize);}
	
	void hashtable_scan(Pointer** nHP){
		int end = capacity()*kElementSize;
		dout(debug_gc,"scan hashtable %i\n",end);
		for(int i=0; i < end;i++){
			int idx = kElementOffset + i * kPointerSize;
			//printf("fw elemetn %i ",idx);
			Pointer* itr = field(idx);
			if(itr != NULL && itr->isHeapObject()){
				setField(idx, ((HeapObject*)itr)->forward(nHP));
			}
		}
		
	}
};

#endif
